/*
* Copyright (c) 2013 Google, Inc.
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef B2_PARTICLE_GROUP
#define B2_PARTICLE_GROUP

#include "b2_particle.h"

class b2Shape;
class b2World;
class b2ParticleSystem;
class b2ParticleGroup;
class b2ParticleColor;
#if LIQUIDFUN_EXTERNAL_LANGUAGE_API
class b2CircleShape;
#endif // LIQUIDFUN_EXTERNAL_LANGUAGE_API

/// @file

/// The particle group type.  Can be combined with the | operator.
enum b2ParticleGroupFlag
{
  /// Prevents overlapping or leaking.
  b2_solidParticleGroup = 1 << 0,
  /// Keeps its shape.
  b2_rigidParticleGroup = 1 << 1,
  /// Won't be destroyed if it gets empty.
  b2_particleGroupCanBeEmpty = 1 << 2,
  /// Will be destroyed on next simulation step.
  b2_particleGroupWillBeDestroyed = 1 << 3,
  /// Updates depth data on next simulation step.
  b2_particleGroupNeedsUpdateDepth = 1 << 4,
  b2_particleGroupInternalMask =
    b2_particleGroupWillBeDestroyed |
    b2_particleGroupNeedsUpdateDepth,
};

/// A particle group definition holds all the data needed to construct a
/// particle group.  You can safely re-use these definitions.
struct b2ParticleGroupDef
{

  b2ParticleGroupDef()
  {
    flags = 0;
    groupFlags = 0;
    position = b2Vec2_zero;
    angle = 0;
    linearVelocity = b2Vec2_zero;
    angularVelocity = 0;
    color = b2ParticleColor_zero;
    strength = 1;
    shape = NULL;
    shapes = NULL;
    shapeCount = 0;
    stride = 0;
    particleCount = 0;
    positionData = NULL;
    lifetime = 0.0f;
    userData = NULL;
    group = NULL;

#if LIQUIDFUN_EXTERNAL_LANGUAGE_API
    circleShapes = NULL;
    ownShapesArray = false;
#endif // LIQUIDFUN_EXTERNAL_LANGUAGE_API
  }

  ~b2ParticleGroupDef()
  {
#if LIQUIDFUN_EXTERNAL_LANGUAGE_API
    FreeShapesMemory();
#endif // LIQUIDFUN_EXTERNAL_LANGUAGE_API
  }

  /// The particle-behavior flags (See #b2ParticleFlag).
  uint32 flags;

  /// The group-construction flags (See #b2ParticleGroupFlag).
  uint32 groupFlags;

  /// The world position of the group.
  /// Moves the group's shape a distance equal to the value of position.
  b2Vec2 position;

  /// The world angle of the group in radians.
  /// Rotates the shape by an angle equal to the value of angle.
  float32 angle;

  /// The linear velocity of the group's origin in world co-ordinates.
  b2Vec2 linearVelocity;

  /// The angular velocity of the group.
  float32 angularVelocity;

  /// The color of all particles in the group.
  b2ParticleColor color;

  /// The strength of cohesion among the particles in a group with flag
  /// b2_elasticParticle or b2_springParticle.
  float32 strength;

  /// The shape where particles will be added.
  const b2Shape* shape;

  /// A array of shapes where particles will be added.
  const b2Shape* const* shapes;

  /// The number of shapes.
  int32 shapeCount;

  /// The interval of particles in the shape.
  /// If it is 0, b2_particleStride * particleDiameter is used instead.
  float32 stride;

  /// The number of particles in addition to ones added in the shape.
  int32 particleCount;

  /// The initial positions of the particleCount particles.
  const b2Vec2* positionData;

  /// Lifetime of the particle group in seconds.  A value <= 0.0f indicates a
  /// particle group with infinite lifetime.
  float32 lifetime;

  /// Use this to store application-specific group data.
  void* userData;

  /// An existing particle group to which the particles will be added.
  b2ParticleGroup* group;

#if LIQUIDFUN_EXTERNAL_LANGUAGE_API
  /// Storage for constructed CircleShapes from an incoming vertex list
  const b2CircleShape* circleShapes;

  /// True if we create the shapes array internally.
  bool ownShapesArray;

  /// Clean up all memory associated with SetCircleShapesFromVertexList
  void FreeShapesMemory();

  /// From a vertex list created by an external language API, construct
  /// a list of circle shapes that can be used to create a b2ParticleGroup
  /// This eliminates cumbersome array-interfaces between languages.
  void SetCircleShapesFromVertexList(void* inBuf,
                     int numShapes,
                     float radius);

  /// Set position with direct floats.
  void SetPosition(float32 x, float32 y);

  /// Set color with direct ints.
  void SetColor(int32 r, int32 g, int32 b, int32 a);
#endif // LIQUIDFUN_EXTERNAL_LANGUAGE_API
};

/// A group of particles. b2ParticleGroup::CreateParticleGroup creates these.
class b2ParticleGroup
{

public:

  /// Get the next particle group from the list in b2_World.
  b2ParticleGroup* GetNext();
  const b2ParticleGroup* GetNext() const;

  /// Get the particle system that holds this particle group.
  b2ParticleSystem* GetParticleSystem();
  const b2ParticleSystem* GetParticleSystem() const;

  /// Get the number of particles.
  int32 GetParticleCount() const;

  /// Get the offset of this group in the global particle buffer
  int32 GetBufferIndex() const;

  /// Does this group contain the particle.
  bool ContainsParticle(int32 index) const;

  /// Get the logical sum of particle flags.
  uint32 GetAllParticleFlags() const;

  /// Get the construction flags for the group.
  uint32 GetGroupFlags() const;

  /// Set the construction flags for the group.
  void SetGroupFlags(uint32 flags);

  /// Get the total mass of the group: the sum of all particles in it.
  float32 GetMass() const;

  /// Get the moment of inertia for the group.
  float32 GetInertia() const;

  /// Get the center of gravity for the group.
  b2Vec2 GetCenter() const;

  /// Get the linear velocity of the group.
  b2Vec2 GetLinearVelocity() const;

  /// Get the angular velocity of the group.
  float32 GetAngularVelocity() const;

  /// Get the position of the group's origin and rotation.
  /// Used only with groups of rigid particles.
  const b2Transform& GetTransform() const;

  /// Get position of the particle group as a whole.
  /// Used only with groups of rigid particles.
  const b2Vec2& GetPosition() const;

  /// Get the rotational angle of the particle group as a whole.
  /// Used only with groups of rigid particles.
  float32 GetAngle() const;

  /// Get the world linear velocity of a world point, from the average linear
  /// and angular velocities of the particle group.
  /// @param a point in world coordinates.
  /// @return the world velocity of a point.
  b2Vec2 GetLinearVelocityFromWorldPoint(const b2Vec2& worldPoint) const;

  /// Get the user data pointer that was provided in the group definition.
  void* GetUserData() const;

  /// Set the user data. Use this to store your application specific data.
  void SetUserData(void* data);

  /// Call b2ParticleSystem::ApplyForce for every particle in the group.
  void ApplyForce(const b2Vec2& force);

  /// Call b2ParticleSystem::ApplyLinearImpulse for every particle in the
  /// group.
  void ApplyLinearImpulse(const b2Vec2& impulse);

  /// Destroy all the particles in this group.
  /// This function is locked during callbacks.
  /// @param Whether to call the world b2DestructionListener for each
  /// particle is destroyed.
  /// @warning This function is locked during callbacks.
  void DestroyParticles(bool callDestructionListener);

  /// Destroy all particles in this group without enabling the destruction
  /// callback for destroyed particles.
  /// This function is locked during callbacks.
  /// @warning This function is locked during callbacks.
  void DestroyParticles();

private:

  friend class b2ParticleSystem;

  b2ParticleSystem* m_system;
  int32 m_firstIndex, m_lastIndex;
  uint32 m_groupFlags;
  float32 m_strength;
  b2ParticleGroup* m_prev;
  b2ParticleGroup* m_next;

  mutable int32 m_timestamp;
  mutable float32 m_mass;
  mutable float32 m_inertia;
  mutable b2Vec2 m_center;
  mutable b2Vec2 m_linearVelocity;
  mutable float32 m_angularVelocity;
  mutable b2Transform m_transform;

  void* m_userData;

  b2ParticleGroup();
  ~b2ParticleGroup();
  void UpdateStatistics() const;

};

inline b2ParticleGroup* b2ParticleGroup::GetNext()
{
  return m_next;
}

inline const b2ParticleGroup* b2ParticleGroup::GetNext() const
{
  return m_next;
}

inline b2ParticleSystem* b2ParticleGroup::GetParticleSystem()
{
  return m_system;
}

inline const b2ParticleSystem* b2ParticleGroup::GetParticleSystem() const
{
  return m_system;
}

inline int32 b2ParticleGroup::GetParticleCount() const
{
  return m_lastIndex - m_firstIndex;
}

inline bool b2ParticleGroup::ContainsParticle(int32 index) const
{
  return m_firstIndex <= index && index < m_lastIndex;
}

inline b2ParticleGroup::~b2ParticleGroup()
{
}

inline int32 b2ParticleGroup::GetBufferIndex() const
{
  return m_firstIndex;
}

inline uint32 b2ParticleGroup::GetGroupFlags() const
{
  return m_groupFlags & ~b2_particleGroupInternalMask;
}

inline float32 b2ParticleGroup::GetMass() const
{
  UpdateStatistics();
  return m_mass;
}

inline float32 b2ParticleGroup::GetInertia() const
{
  UpdateStatistics();
  return m_inertia;
}

inline b2Vec2 b2ParticleGroup::GetCenter() const
{
  UpdateStatistics();
  return m_center;
}

inline b2Vec2 b2ParticleGroup::GetLinearVelocity() const
{
  UpdateStatistics();
  return m_linearVelocity;
}

inline float32 b2ParticleGroup::GetAngularVelocity() const
{
  UpdateStatistics();
  return m_angularVelocity;
}

inline const b2Transform& b2ParticleGroup::GetTransform() const
{
  return m_transform;
}

inline const b2Vec2& b2ParticleGroup::GetPosition() const
{
  return m_transform.p;
}

inline float32 b2ParticleGroup::GetAngle() const
{
  return m_transform.q.GetAngle();
}

inline b2Vec2 b2ParticleGroup::GetLinearVelocityFromWorldPoint(
                        const b2Vec2& worldPoint) const
{
  UpdateStatistics();
  return m_linearVelocity + b2Cross(m_angularVelocity, worldPoint - m_center);
}

inline void* b2ParticleGroup::GetUserData() const
{
  return m_userData;
}

inline void b2ParticleGroup::SetUserData(void* data)
{
  m_userData = data;
}

inline void b2ParticleGroup::DestroyParticles()
{
  DestroyParticles(false);
}

#if LIQUIDFUN_EXTERNAL_LANGUAGE_API
inline void b2ParticleGroupDef::SetPosition(float32 x, float32 y)
{
  position.Set(x, y);
}

inline void b2ParticleGroupDef::SetColor(int32 r, int32 g, int32 b, int32 a)
{
  color.Set((uint8)r, (uint8)g, (uint8)b, (uint8)a);
}
#endif // LIQUIDFUN_EXTERNAL_LANGUAGE_API


#endif
